package realDeal;

import java.util.HashMap;

public class BRICK {
	/**
	 * buckets- an array of the buckets used by BRICK.
	 */
	private Bucket[] buckets;
	/**
	 * objectToSerial- a map from an object to its counter number. the counter
	 * numbers are in range from 0 to TOTAL_OBJECTS-1 (aka. N-1)
	 */
	private HashMap<Object, Integer> objectToSerial;
	/**
	 * Counts how many different object have arrived so far.
	 */
	private int objectCounter;

	/**
	 * saves vital information regarding current execution (see Statistics for
	 * further info)
	 */
	private Statistics stats;

	public BRICK(int maxIncrements, int maxDiffrentObjects, int amountOfSteps,
			int[] stepSizes, int[] widths, boolean testMode) {
		Statics.TOTAL_INCREMENTS = maxIncrements;
		Statics.TOTAL_OBJECTS = maxDiffrentObjects;
		Statics.MAX_WIDTH = Integer.SIZE
				- Integer.numberOfLeadingZeros(maxIncrements);
		Statics.BUCKET_SIZE = stepSizes[0]; // k0
		int amountOfBuckets = (int) Math.ceil(Statics.TOTAL_OBJECTS
				/ Statics.BUCKET_SIZE);
		Statics.AMOUNT_BOUCKET=amountOfBuckets;
		buckets = new Bucket[amountOfBuckets];
		initializeBuckets(stepSizes, widths, amountOfSteps, amountOfBuckets);
		this.objectCounter = 0;
		this.objectToSerial = new HashMap<Object, Integer>();
		if (testMode) {
			this.stats = new Statistics(amountOfBuckets);
		}
	}

	private void initializeBuckets(int[] stepSizes, int[] widths,
			int amountOfSteps, int amountOfBuckets) {
		for (int i = 0; i < amountOfBuckets; i++) {
			buckets[i] = new Bucket(stepSizes, widths, amountOfSteps);
		}
	}

	private int getNextLocation(){
		int nextCounter= getNextCounterNumber();
		int nextLocation=(nextCounter%Statics.AMOUNT_BOUCKET)*Statics.BUCKET_SIZE+nextCounter/Statics.AMOUNT_BOUCKET;
		return nextLocation;
	}
	
	private int getNextCounterNumber() {
		return objectCounter++;
	}

	/**
	 * 
	 * @param obj
	 * @return the number of occurrences of the given object so far
	 */
	public int lookup(Object obj) {
		if (objectToSerial.containsKey(obj)) {
			int index = objectToSerial.get(obj);
			return buckets[index / Statics.BUCKET_SIZE].getValueOfCounter(index
					% Statics.BUCKET_SIZE);
		}
		return 0;
	}

	public void increment(Object obj) {
		int objLocation;
		if (objectToSerial.containsKey(obj)) {
			objLocation = objectToSerial.get(obj);
		} else {
			objLocation = getNextLocation();
			objectToSerial.put(obj, objLocation);
		}
		int bucketIndex = objLocation / Statics.BUCKET_SIZE;
		buckets[bucketIndex].incrementCounter((objLocation % Statics.BUCKET_SIZE));
	}

	public Statistics close() {
		if (stats != null) {
			// amount of buckets is already set
			for (int i = 0; i < buckets.length; i++) {
				if (!buckets[i].isEmpty()) { // found used bucket
					stats.increaseUsedBuckets();
				}
				if (buckets[i].isOverflowed()) { // found overflowed bucket
					stats.increaseOverflowedBuckets();
				}
			}
		}
		return this.stats;
	}
}
